package com.nf.mvc.exception;

import com.nf.mvc.MethodArgumentResolver;
import com.nf.mvc.argument.MethodArgumentResolverComposite;
import com.nf.mvc.argument.MethodParameter;
import com.nf.mvc.handler.HandlerMethod;
import com.nf.mvc.support.MethodInvoker;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

/**
 * 此异常解析器让异常处理方法能支持的参数除了异常以外,还支持handler方法所能使用的的参数类型.
 * 此异常解析器需要与{@link  ExceptionHandlers}注解配合使用
 * <h3>典型用法</h3>
 * <pre class="code">
 *    &#064;ExceptionHandlers({AException.class,BException.class})
 *    public JsonViewResult handleArithmeticException(String a,AException re){
 *        System.out.println("异常处理方法参数a = " + a);
 *        return new JsonViewResult(new ResponseVO(10002,"异常消息:" + re.getMessage(),"a异常--"));
 *    }
 * </pre>
 * <h3>功能限制</h3>
 * 这里在解析参数时没有考虑HttpServletRequest已经被解析读取过的情况,意思就是假定已经在handler的方法参数解析过程中已经读取了输入流
 * 并关闭了(比如在反序列化解析时),这里仍然需要读取流的话就会抛出异常,框架目前是不支持这种情况的
 */
public class ParameterizedExceptionHandlersExceptionResolver extends ExceptionHandlersExceptionResolver {
    @Override
    protected Object executeExceptionHandlerMethod(HandlerMethod exceptionHandlerMethod, Exception exposedException, HttpServletRequest request) throws Exception {
        MethodArgumentResolverComposite argumentResolver = MethodArgumentResolverComposite.defaultInstance();
        // 这里要用insert相关方法添加,用add相关方法添加到参数解析器集合的最后的话，异常参数就会用BeanMethodArgumentResolver进行解析了
        argumentResolver.insertResolvers(new ExceptionArgumentResolver(exposedException));

        MethodInvoker methodInvoker = new MethodInvoker(argumentResolver);
        Object instance = exceptionHandlerMethod.getHandlerObject();
        Method method = exceptionHandlerMethod.getMethod();

        return methodInvoker.invoke(instance, method, request);
    }

    private static class ExceptionArgumentResolver implements MethodArgumentResolver {
        private final Exception raisedException;

        public ExceptionArgumentResolver(Exception raisedException) {
            this.raisedException = raisedException;
        }

        @Override
        public boolean supports(MethodParameter parameter) {
            return parameter.getType().isAssignableFrom(raisedException.getClass());
        }

        /**
         * 异常参数的解析器
         * <p>这里写注释主要是为了说明子类重写父类的方法或者实现接口的方法时，方法签名的throws语句该如何处理的问题，大致的规则如下</p>
         * <ul>
         *     <li>父类型没有throws，子类型只能声明非检查异常</li>
         *     <li>父类型声明了异常，子类型只能声明父类声明异常及其子异常或者运行时异常</li>
         *     <li>父类型声明了异常，子类型可以不声明异常</li>
         * </ul>
         *
         * <p>子类型虽然可以不声明throws语句，但子类方法实现时，如果真的抛出了异常，还是要在子类方法上添加上throws语句的</p>
         *
         * <h3>参考资料</h3>
         * <a href="https://www.geeksforgeeks.org/exception-handling-with-method-overriding-in-java/">throws的处理</a>
         *
         * @param parameter 方法参数
         * @param request   servlet请求对象
         * @return 解析后的异常参数值
         */
        @Override
        public Object resolveArgument(MethodParameter parameter, HttpServletRequest request) {
            return this.raisedException;
        }
    }
}
